import { Box3, Frustum, MathUtils, Matrix4, Object3D, PerspectiveCamera, Vector3 } from "three";
import { App, GeoUtils, Point } from "../Engine";
import { EventsDispatch } from "./EventsDispatch";
import { Tween } from "../utils/Tween";
import Events from "../application/constants/Events";
import { InteractionEvent } from "../processor/Mouse";
import Const from "../application/constants/Const";
import { MapLevelController } from "../processor/ZoomProcessor/MapLevelController";
import Helper from "../utils/Helper";
import CameraUtils from "../renderExtension/utils/CameraUtils";
import Orbit from "../processor/Orbit";

/**
 * 相机对象
 */
class Camera extends EventsDispatch {
    private threeCamera: PerspectiveCamera;
    private app: App;
    /**
     * 相机位置
     */
    public position: any;
    /**
     * 相机看向的焦点位置
     */
    public lookAtTarget: Vector3;
    /**
     * 最近可视范围
     */
    public near: number;
    /**
     * 最远可视范围
     */
    public far: number;
    public orbit: Orbit;
    private maxAngle: number;
    private mapLevelController: MapLevelController;
    private frustum: Frustum = new Frustum();
    private wanderPath: Array<{ eyePosition: Point | Vector3, eyeTargetPosition: Point | Vector3, flyTime: number }>; // 漫游路径数据
    private debounceWheel;
    // private isOpenVertical; //是否开启相机垂直的功能
    public cameraUtils: CameraUtils;
    private curWanderFly;

    constructor() {
        super();
        this.app = App.getInstance();
        this.threeCamera = this.app.three.camera;
        this.mapLevelController = new MapLevelController();
        this.debounceWheel = Helper.debounce2(this.wheel.bind(this), 10, false); //this.wheel.bind(this); //
        this.cameraUtils = CameraUtils.getInstance();
        // this.isOpenVertical = (this.app.options.Map.verticalLevel != 1);

        this.initPropertyListener();
        this.register();
    }

    //添加和相机做碰撞检测的物体
    public addIntersectObjWithEye(obj: Object3D) {
        this.cameraUtils.addIntersectObjWithEye(obj);
    }

    //移除和相机做碰撞检测的物体
    public removeIntersectObjWithEye(obj: Object3D) {
        this.cameraUtils.removeIntersectObjWithEye(obj);
    }

    /**
     * 页面大小变化时会触发这个事件,也可以主动触发
     */
    protected resize() {
        let size = this.app.getSize();
        this.threeCamera.aspect = size.getWidth() / size.getHeight();
        this.threeCamera.updateProjectionMatrix();
        this.threeCamera.updateMatrixWorld(true);
    }

    private isLastWheelFinish = true;
    private wheelEventDataArray: Array<InteractionEvent> = new Array<InteractionEvent>();
    protected wheel(e: InteractionEvent) {
        if (!this.isLastWheelFinish) { // && !this.isOpenVertical
            this.wheelEventDataArray.push(e);
            return;
        }

        let maxZoom = Number(this.app.options.Map.maxZoom);
        let minZoom = Number(this.app.options.Map.minZoom);
        //到了最大层级，继续放大就不处理了；到了最小层级，继续缩小也不处理了
        if ((this.app.map.zoom == maxZoom && e.deltaY < 0) || (this.app.map.zoom == minZoom && e.deltaY > 0) || e.deltaY == 0) {
            //清空this.wheelEventDataArray
            this.wheelEventDataArray.splice(0, this.wheelEventDataArray.length);
            return;
        }

        if (this.checkCameraCollision()) { //如果相机碰撞了，则不处理
            return;
        }

        this.app.mouse.setCameraBeforeWheel(e);

        this.app.fireEvent(Events.wheelChange);

        //这里的算法思路：(camera.position - lookAtTarget)向量的长度拉长，lookAtTarget的位置加上拉长的向量，得到新的相机的位置
        let rayVector: Vector3 = this.threeCamera.position.clone().sub(this.lookAtTarget);
        let rayLength = rayVector.length();
        let normalizeRay: Vector3 = rayVector.normalize();
        let scale = 150;
        let isDown = false;
        if (e.deltaY > 0) {
            scale *= 1;
            isDown = false;
        } else if (e.deltaY < 0) {
            scale *= -1;
            isDown = true;
        }
        let cameraScrollSpeed = this.app.options.Camera.cameraScrollSpeed;
        let offset = rayLength / (scale / cameraScrollSpeed);
        let newRayLength = rayLength + offset;
        let newCameraPosition = this.lookAtTarget.clone().add(normalizeRay.multiplyScalar(newRayLength));

        if (this.app.options.Camera.isOpenEyeIntersect) {
            let adjustMsg = this.cameraUtils.adjustCameraPosition(this.threeCamera.position, this.lookAtTarget, newCameraPosition, this.lookAtTarget);
            if (adjustMsg.isCollisionEye) {
                return;
            }
        }

        // if(this.isOpenVertical){
        // this.threeCamera.position.set(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z); //用这段代码会导致相机位置跳动，然后出现地图和雾的抖动
        // let nextZoom = this.getMapLevel();
        // if (this.app.map.zoom != nextZoom) {
        //     this.app.map.zoom = nextZoom;
        //     this.app.fireEvent(Events.ZoomChange, isDown);
        // }
        // this.app.camera.orbit.setCameraAfterWheel(e);
        // }else {
        let flyTime = 1; //cameraScrollSpeed * 1;
        let self = this;
        this.isLastWheelFinish = false;
        //相机平滑的飞行，这样做是为了防止相机突然移动，导致地图和雾的抖动
        this.flyTo(newCameraPosition, this.lookAtTarget, flyTime, function () {
            self.isLastWheelFinish = true;

            self.app.mouse.setCameraAfterWheel(e);

            if (self.wheelEventDataArray.length > 0) {
                let nextEventData = self.wheelEventDataArray.shift();
                self.wheel(nextEventData);
            }

        }, true);
        // }

        // this.app.camera.orbit.setCameraAfterWheel(e);
    }

    private checkCameraCollision() {
        if (this.app.camera.cameraUtils.isCollisionEye) { //如果相机已经碰撞了，则等待500毫秒后把isCollisionEye置为false
            let self = this;
            setTimeout(() => {
                if (self.app.camera.cameraUtils.isCollisionEye) {
                    self.app.camera.cameraUtils.isCollisionEye = false;
                }
            }, 300);
            return true;
        }
        return false;
    }

    public getMapLevel(isConsiderLevelRange = true) {
        let mapLevel;
        try {
            mapLevel = this.mapLevelController.computeMapLevel(isConsiderLevelRange);
            if (this.app.options.Camera.isDynamicNear) {
                let cameraNer;
                let maxZoom = Number(this.app.options.Map.maxZoom);
                if (mapLevel >= 18) {
                    cameraNer = Math.pow(((maxZoom + 1) - mapLevel), 2.2);
                } else {
                    cameraNer = ((maxZoom + 1) - mapLevel) * 20;
                }

                // 把cameraNer只保留两位小数
                cameraNer = parseFloat(cameraNer.toFixed(2));
                this.threeCamera.near = cameraNer; //相机距离地面很近的时候，near会变小；相机距离地面边远的时候，near会变大。这样做是为了避免深度冲突
                this.threeCamera.updateProjectionMatrix(); //修改了相机的near, 必须更新相机的投影矩阵
            }
        } catch (error) {
            mapLevel = this.app.map.zoom;
        }
        return mapLevel;
    }

    private rotateEnd() {
        let nextZoom = this.getMapLevel();
        if (this.app.map.zoom != nextZoom) {
            this.app.map.zoom = nextZoom;
            this.app.fireEvent(Events.ZoomChange);
        }
    }

    private register() {
        this.app.on(Events.CameraChange, this.updateFrustum.bind(this));
        this.app.on(Events.Resize, this.resize.bind(this));
        this.app.on(Events.MouseWheel, this.debounceWheel);
        this.app.on(Events.MapRotateEnd, this.rotateEnd.bind(this));
    }

    public dispose() {
        this.app.off(Events.CameraChange, this.updateFrustum.bind(this));
        this.app.off(Events.Resize, this.resize);
        this.app.off(Events.MouseWheel, this.debounceWheel);
        this.app.off(Events.MapRotate, this.debounceWheel);
    }

    /**
     * 初始化属性监听器
     */
    private initPropertyListener() {
        let scope = this;

        let propertyList = ['near', 'far'];
        this.addPropertyListener(propertyList, {
            getter: function (propertyName) { return scope.threeCamera[propertyName] },
            setter: function (propertyName, value) { scope.threeCamera[propertyName] = value }
        });
        this.addPropertyListener('position', {
            getter: function () { return scope.threeCamera.position },
            setter: function (value) { scope.threeCamera.position.set(value.x, value.y, value.z ? value.z : 0) }
        });
    }

    /**
    * 修改相机焦点（看向位置）
    * @param target 新的相机焦点
    * @example  let lookAtPosition = new GE.Point(119, 30, 0, '4326').toEPSGWeb();
       app.camera.lookAt(lookAtPosition);
    */
    public lookAt(target: Point | Vector3) {
        this.threeCamera.lookAt(target);
        this.lookAtTarget = target;
        this.app.options.Camera.lookAt = target;
    }

    //判断相机是否移动
    private isCameraMove(startEye: Vector3, endEye: Point | Vector3, isMouseScroll?: boolean){
        let cameraPosition = new Point(startEye.x, startEye.y, startEye.z, Const.EPSGType.EPSGWeb).toGeography();
        let cameraTargetPosition;
        if(endEye instanceof Point){
            cameraTargetPosition = endEye.toGeography();
        }else{
            cameraTargetPosition = new Point(endEye.x, endEye.y, endEye.z, Const.EPSGType.EPSGWeb).toGeography();
        }

        if(!isMouseScroll && cameraPosition.clone().sub(cameraTargetPosition).length() < 0.00001){
            return false;
        }
        return true;
    }

    /**
     * 相机从当前视角跳转到指定位置
     * @param endEye 要跳转到的视角所在相机位置
     * @param endTarget 要跳转到的视角所在相机焦点位置
     * @param time 跳转时间，0为瞬间跳转，默认2000
     * @param onCompleteCallback 相机跳转到目的地后需要调用的回调函数
     * @example  let cameraPosition = new GE.Point(119, 30, 1000, '4326').toEPSGWeb();
        let lookAtPosition = new GE.Point(119, 30, 0, '4326').toEPSGWeb();
        app.camera.flyTo(cameraPosition, lookAtPosition, 100);
     */
    public flyTo(endEye: Point | Vector3, endTarget: Point | Vector3, time: number = 2000, onCompleteCallback?, isMouseScroll?: boolean) {
        let startEye = this.position.clone();
        let startTarget = this.lookAtTarget.clone();

       if(!this.isCameraMove(startEye, endEye, isMouseScroll)){ //相机没有移动
         return;
       }

        //@ts-ignore
        let flyTween = this.fly(startEye, startTarget, endEye, endTarget, time, onCompleteCallback, isMouseScroll);
        return flyTween;
    }

    /**
     * 相机从某个位置(非当前视角)跳转到另一个位置
     * @param startEye 起始位置
     * @param startTarget 起始相机焦点
     * @param endEye 终止位置
     * @param endTarget 终止相机焦点
     * @param time 跳转时间啊，0为瞬间跳转，默认2000
     */
    public fly(startEye: Point | Vector3, startTarget: Point | Vector3, endEye: Point | Vector3, endTarget: Point | Vector3, time: number = 2000, onCompleteCallback?, isMouseScroll?: boolean) {
        //调整飞行结束时相机位置
        if (startEye instanceof Point) startEye = startEye.toEPSGWeb();
        if (startTarget instanceof Point) startTarget = startTarget.toEPSGWeb();
        if (endEye instanceof Point) endEye = endEye.toEPSGWeb();
        if (endTarget instanceof Point) endTarget = endTarget.toEPSGWeb();

        let start = {
            ex: startEye.x,
            ey: startEye.y,
            ez: startEye.z,
            tx: startTarget.x,
            ty: startTarget.y,
            tz: startTarget.z
        }
        let end = {
            ex: endEye.x,
            ey: endEye.y,
            ez: endEye.z,
            tx: endTarget.x,
            ty: endTarget.y,
            tz: endTarget.z
        }
        let self = this;
        let tween = new Tween(start).to(end, time)
            .onUpdate(() => {
                self.app.fireEvent(Events.recoreCameraMsg); //记录相机信息

                let eye = new Vector3(start.ex, start.ey, start.ez);
                let target = new Vector3(start.tx, start.ty, start.tz);
                self.position.copy(eye);
                self.lookAt(target);

                self.app.fireEvent(Events.MapRange); //限制地图的范围
                
                let isConsiderLevelRange = true; //考虑地图层级的范围
                let nextZoom = self.getMapLevel(isConsiderLevelRange);
                if (self.app.map.zoom != nextZoom) {
                    self.app.map.zoom = nextZoom;
                    self.app.fireEvent(Events.ZoomChange);
                }
                self.app.fireEvent(Events.ForceRefreshMap); //强制刷新
                self.app.fireEvent(Events.AngleChange);
            }).onComplete(() => {
                tween.stop();
                tween = null;

                let isConsiderLevelRange = true; //考虑地图层级的范围
                let nextZoom = this.getMapLevel(isConsiderLevelRange);
                if (this.app.map.zoom != nextZoom) {
                    this.app.map.zoom = nextZoom;
                    this.app.fireEvent(Events.ZoomChange);
                }
                
                self.app.fireEvent(Events.ForceRefreshMap); //强制刷新
                self.app.fireEvent(Events.AngleChange);

                if (onCompleteCallback) {
                    onCompleteCallback();
                }

                if (!isMouseScroll) { //非鼠标滑动的层级切换，会有瓦片内存泄露。需要把非当前层级的瓦片删除掉
                    self.app.map.removeTileNoZoom();
                }
            });
        tween.start();
        return tween;
    }

    /**
     * 相机漫游
     * @param path 相机漫游的路径，该路径是一个由多个点组成的数组
     * @param path.eyePosition 漫游路径上相机的一个位置
     * @param path.eyeTargetPosition 漫游路径上相机看向的一个目标点
     * @param time 相机漫游的时间，单位毫秒
     * @param onCompleteCallback 相机漫游结束后的回调函数
     */
    public wander(path: Array<{ eyePosition: Point | Vector3, eyeTargetPosition: Point | Vector3 }>, time: number, onFinishCallback?) {
        if (path.length == 0) {
            return;
        }

        this.wanderPath = new Array<{ eyePosition: Point, eyeTargetPosition: Point, flyTime: number }>(); // 重置漫游路径

        let firstPoint = path[0];
        //如果起点不是当前相机位置, 就把相机位置添加到头部
        if (firstPoint.eyePosition.x != this.position.x || firstPoint.eyePosition.y != this.position.y || firstPoint.eyePosition.z != this.position.z) {
            //往path数组的最前面添加一个点
            path.unshift({
                eyePosition: this.position.clone(),
                eyeTargetPosition: this.lookAtTarget.clone(),
            });
        }

        //计算path数组中所有点之间的距离之和
        let totalDistance = 0;
        for (let i = 0; i < path.length - 1; i++) {
            let currentPoint = path[i];
            if (currentPoint.eyePosition instanceof Point) {
                currentPoint.eyePosition.toEPSGWeb();
            }
            let nextPoint = path[i + 1];
            if (nextPoint.eyePosition instanceof Point) {
                nextPoint.eyePosition.toEPSGWeb();
            }

            let distance = currentPoint.eyePosition.distanceTo(nextPoint.eyePosition);
            totalDistance += distance;
        }

        //计算每两个点之间距离和总距离之间的百分比, 然后计算出每两个点之间需要飞行的时间
        for (let i = 1; i < path.length; i++) {
            let prreviousPoint = path[i - 1];
            let currentPoint = path[i];
            let distance = prreviousPoint.eyePosition.distanceTo(currentPoint.eyePosition);
            let ratio = distance / totalDistance;
            let flyTime = ratio * time;

            this.wanderPath.push({
                eyePosition: currentPoint.eyePosition.clone(),
                eyeTargetPosition: currentPoint.eyeTargetPosition.clone(),
                flyTime: flyTime
            })
        }

        let nextTarget = this.wanderPath.shift();
        this.curWanderFly = this.flyTo(nextTarget.eyePosition, nextTarget.eyeTargetPosition, nextTarget.flyTime, this.wanderCallback.bind(this, onFinishCallback));
    }

    /**
     * 停止漫游, 在wander方法调用后调用
     */
    public stopWander() {
        if (this.curWanderFly) {
            this.curWanderFly.stop();
            this.curWanderFly = null;
        }
    }

    private wanderCallback(onFinishCallback?) { //漫游的回调
        if (this.wanderPath.length == 0) { //漫游结束
            if (onFinishCallback) {
                onFinishCallback();
            }
            this.curWanderFly = null;
            return;
        }
        let nextTarget = this.wanderPath.shift();
        this.curWanderFly = this.flyTo(nextTarget.eyePosition, nextTarget.eyeTargetPosition, nextTarget.flyTime, this.wanderCallback.bind(this, onFinishCallback));
    }

    /**
     * 获取当前相机信息
     * @returns 相机信息
     */
    public info(): { position: Point, target: Point } {
        let position = new Point().set(this.position.x, this.position.y, this.position.z, Const.EPSGType.EPSGWeb).toGeography();
        let target = new Point().set(this.lookAtTarget.x, this.lookAtTarget.y, this.lookAtTarget.z, Const.EPSGType.EPSGWeb).toGeography();
        return { position: position, target: target };
    }

    private updateFrustum() {
        // 获取相机的投影矩阵和视图矩阵
        var projectionMatrix = this.threeCamera.projectionMatrix;
        var viewMatrix = this.threeCamera.matrixWorldInverse;
        // 构建剪裁空间矩阵
        var clipMatrix = new Matrix4().multiplyMatrices(projectionMatrix, viewMatrix);
        // 创建一个Frustum对象
        // this.frustum  = new Frustum().setFromProjectionMatrix(clipMatrix);
        this.frustum.setFromProjectionMatrix(clipMatrix);
    }

    public checkVisible(objBox: Box3) {
        if (!this.frustum) {
            return false;
        }

        let isIntersect = this.frustum.intersectsBox(objBox);
        return isIntersect;
    }

    /**
     * 指定地图到特定的层级。调用该方法，相机会自动调整高度，使地图层级达到指定的层级，并且相机垂直于地图平面。
     * @param zoomParam 指定地图层级需要用到的参数
     * @param zoomParam.mapZoom 地图的层级
     * @param zoomParam.cameraLookAt 相机看向的位置, 可选参数，如果不传，则相机看向当前的lookAtTarget
     */
    public setMapZoom(zoomParam: { mapZoom: number, cameraLookAt?: Point }) {
        this.mapLevelController.setMapZoom(zoomParam);
    }

    /**
     * 缩放地图
     * @param isZoomIn true表示放大，false表示缩小
     */
    public zoomMap(isZoomIn: boolean) {
        this.mapLevelController.zoomMap(isZoomIn);
    }

    /**
     * 设置相机的可视范围
     * @param range 地图的可视范围
     * @param range.startPoint 范围的起始点
     * @param range.endPoint 范围的结束点
     */
    public setVisualScope(range: { startPoint: Point, endPoint: Point }, onCompleteCallback?) {
        this.mapLevelController.setVisualScope(range, onCompleteCallback);
    }
}
export { Camera }
